Android RSA加密解密

需求

在Android上实现RSA 2048 PKCS#1加密,秘钥不能放在Java层(其实公钥被别人看了也没啥吧?)。

几个思路:

  1. 所有加密的程序都在native层。
  2. 秘钥在Native层,加密在Java实现。
  3. 所有都在Java实现。(不满足公钥不能放在Java层的要求)

RSA加密

RSA是一种非对称加密算法。

通常的使用流程是A生成一对秘钥,公钥私钥,而这对秘钥有这样的特性:

使用公钥加密的数据,利用私钥进行解密,使用私钥加密的数据,利用公钥进行解密

A把公钥交给B,B使用公钥加密后把数据给回A,A使用私钥进行解密。

2048是RSA密钥长度bit数,数字越大安全性越大。

PKCS#1是公钥加密标准(Public Key Cryptography Standards, PKCS),是秘钥的一种格式。

这里可以体验一下。

Android在native中使用OpenSSL

项目地址:https://github.com/etet2007/RSAencrypt

在Android中添加native代码的步骤在官网中说得很清楚了。

总的来说有几个Point。

  1. cpp文件夹下用于解密的cpp文件。Java层调用的native方法与其关联。
//Java

public static native byte[] encodeByRSAPubKey(byte[] src);
//CPP

extern "C" JNIEXPORT jbyteArray JNICALL
Java_lau_stephen_rsaencrypt_EncryptUtils_encodeByRSAPubKey(JNIEnv *env, jclass type,
                                                           jbyteArray src_) {
    std::string keys = "-----BEGIN PUBLIC KEY-----\n"
                       "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwm/vZlgdvA/s3o+Epq2B\n"
                       "TF9Pk1goY4+wji1fjkmePasZkt12Rx0A0qXuzBfe8K4Y1uf/sKD1XHeXtvPol5TF\n"
                       "ZKq6dEQpd3PsweFMFGYfZbA5IdwEQWXFJqJSpru/jXENCanUARVV5Au0fjaMw71x\n"
                       "dGbHQ7gNdxln9xeoPkyCLBuWor5B3U47NFGEz8ZMELCib0+9bPtzIVuBYA5BsT9A\n"
                       "WgHZpuRZRgQ2r3a0ehe7gO1H+SKrLStVzUZ7EUW4PBM4IhIrR+BfORHi4PgD+4rZ\n"
                       "IuMzg99Y20ytaHIm6tw6+dvt3gSY2q2VWITCVE2dtH167R/AR+mJDFhp89Ss1sGE\n"
                       "wQIDAQAB\n"
                       "-----END PUBLIC KEY-----";

    jbyte *src = env->GetByteArrayElements(src_, NULL);

    jsize src_Len = env->GetArrayLength(src_);

    int encryptedValueSize = 0, src_flen = 0, cipherText_offset = 0, desText_len = 0, src_offset = 0;

    //BIO_new_mem_buf() creates a memory BIO using len bytes of data at buf,
    // if len is -1 then the buf is assumed to be nul terminated and its length is determined by strlen.
    BIO *keyBio = BIO_new_mem_buf((void *) keys.c_str(), -1);
    //The RSA structure consists of several BIGNUM components.
    // It can contain public as well as private RSA keys:
    RSA *publicKey = PEM_read_bio_RSA_PUBKEY(keyBio, NULL, NULL, NULL);
    //释放BIO
    BIO_free_all(keyBio);

    //RSA_size returns the RSA modulus size in bytes.
    // It can be used to determine how much memory must be allocated for an RSA encrypted value.
    int flen = RSA_size(publicKey);

    //复制src到srcOrigin
    unsigned char *srcOrigin = (unsigned char *) malloc(src_Len);
    memset(srcOrigin, 0, src_Len);
    memcpy(srcOrigin, src, src_Len);
    //每次加密后的长度
    unsigned char *encryptedValue = (unsigned char *) malloc(flen);

    desText_len = flen * (src_Len / (flen - 11) + 1);
    unsigned char *desText = (unsigned char *) malloc(desText_len);
    memset(desText, 0, desText_len);

    //对数据进行公钥加密运算
    //对于1024bit,2048应该为256
    //RSA_PKCS1_PADDING 最大加密长度 为 128 -11
    //RSA_NO_PADDING 最大加密长度为  128
    //rsa_size = rsa_size - RSA_PKCS1_PADDING_SIZE;
    for (int i = 0; i <= src_Len / (flen - 11); i++) {
        src_flen = (i == src_Len / (flen - 11)) ? src_Len % (flen - 11) : flen - 11;
        if (src_flen == 0) {
            break;
        }
        //重置encryptedValue
        memset(encryptedValue, 0, flen);
        //encrypt srcOrigin + src_offset到encryptedValue
        //returns the size of the encrypted data
        encryptedValueSize = RSA_public_encrypt(src_flen, srcOrigin + src_offset, encryptedValue,
                                                publicKey, RSA_PKCS1_PADDING);
        if (encryptedValueSize == -1) {
            RSA_free(publicKey);
            CRYPTO_cleanup_all_ex_data();
            env->ReleaseByteArrayElements(src_, src, 0);
            free(srcOrigin);
            free(encryptedValue);
            free(desText);

            return NULL;
        }
        //复制encryptedValue到desText + cipherText_offset
        memcpy(desText + cipherText_offset, encryptedValue, encryptedValueSize);

        cipherText_offset += encryptedValueSize;
        src_offset += src_flen;
    }

    RSA_free(publicKey);
    //清除管理CRYPTO_EX_DATA的全局hash表中的数据,避免内存泄漏
    CRYPTO_cleanup_all_ex_data();

    //从jni释放数据指针
    env->ReleaseByteArrayElements(src_, src, 0);

    jbyteArray cipher = env->NewByteArray(cipherText_offset);

    //在堆中分配ByteArray数组对象成功,将拷贝数据到数组中
    env->SetByteArrayRegion(cipher, 0, cipherText_offset, (jbyte *) desText);
    //释放内存
    free(srcOrigin);
    free(encryptedValue);
    free(desText);

    return cipher;
}
  1. 放置OpenSSL的库于项目中。这里的库是.a的静态库或者.so的动态库,可以自行编译或下载别人的来用。
    动态库静态库编译

在Gradle中设置jniLibs的地址,把带有so库目录地址告诉Gradle,在打包的时候才会把so库打进APK。

    sourceSets.main {
        jniLibs.srcDirs = ['libs/lib']
    }
  1. 提供CMake文件

Gradle提供CMakeList.txt文件的路径,我这里是放在app下。记住路径都是相对路径来的。

    externalNativeBuild {
        cmake {
            path file('CMakeLists.txt')
        }
    }

CMakeList.txt文件,告诉系统用encrypt.cpp编译动态库,其中引用到OpenSSL的crypto、ssl动态库。

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

include_directories(libs/include)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        encrypt
        # Sets the library as a shared library.
        SHARED
        # Provides a relative path to your source file(s).
        src/main/cpp/encrypt.cpp)

add_library(crypto SHARED IMPORTED)
add_library(ssl SHARED IMPORTED)

set_target_properties(crypto PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libcrypto.so)
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/libs/lib/${ANDROID_ABI}/libssl.so)

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

target_link_libraries( # Specifies the target library.
        encrypt
        crypto
        ssl
        # Links the target library to the log library
        # included in the NDK.
        ${log-lib} )

加密在Java实现

Android 密钥库系统

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,158评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,600评论 1 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,785评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,655评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,075评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,002评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,146评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,918评论 0 211
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,671评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,838评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,318评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,636评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,343评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,187评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,982评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,126评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,934评论 2 279